#!/bin/env python

from rdflib import Namespace as NS
from Ft.Rdf import OBJECT_TYPE_RESOURCE, OBJECT_TYPE_LITERAL, OBJECT_TYPE_UNKNOWN

import re

NORMALIZE_PATSUB = re.compile('[\t\n\r\f\v]+')

OWL = NS('http://www.w3.org/2002/07/owl#')
RDF = NS('http://www.w3.org/1999/02/22-rdf-syntax-ns#')
RDFS = NS('http://www.w3.org/2000/01/rdf-schema#')
DC = NS('http://purl.org/dc/elements/1.1/')
DCTERMS = NS('http://purl.org/dc/terms/')
DCTYPES = NS('http://purl.org/dc/dcmitype/')
VS = NS('http://www.w3.org/2003/06/sw-vocab-status/ns#')
WOT = NS('http://xmlns.com/wot/0.1/')
DAMLOIL = NS('http://www.w3.org/2001/10/daml+oil#')


class ImportType(object):    
    
    rules = {
        str(RDF['type'])            : '',
        str(DC['title'])            : 'document',
        str(DC['description'])      : 'document',
        str(DC['language'])         : 'document',
        str(DCTERMS['issued'])      : 'document',
        str(DCTERMS['modified'])    : 'document',
        str(DC['date'])             : 'document',
        str(DC['publisher'])        : 'document',
        
        str(DCTERMS['isReferencedBy'])   : 'relations',
        str(DCTERMS['isRequiredBy'])     : 'relations',
        str(DC['type'])             : 'relations',
        str(DC['source'])           : 'relations',
        str(DCTERMS['hasVersion'])  : 'relations',
        str(DCTERMS['requires'])    : 'relations',
        str(DCTERMS['references'])  : 'relations',
        str(RDFS['seeAlso'])        : 'relations',
        
        str(RDFS['label'])          : 'schema',
        str(RDFS['comment'])        : 'schema',        
        str(RDFS['isDefinedBy'])    : 'schema',
        
        str(OWL['sameAs'])          : 'schema-sets',
        str(OWL['unionOf'])                : 'schema-sets',
        str(OWL['disjointWith'])           : 'schema-sets',
        str(OWL['complementOf'])           : 'schema-sets',
        str(OWL['inverseOf'])              : 'schema-sets',
        str(DAMLOIL['unionOf'])            : 'schema-sets',
        str(DAMLOIL['complementOf'])       : 'schema-sets',
        
        str(VS['term_status'])      : 'schema',
                
    }    
    

class OntologyType(ImportType):
    rules = ImportType.rules.copy()
    rules.update({
        str(OWL['imports'])            : 'schema-relations',
        str(DAMLOIL['imports'])        : 'schema-relations',
        str(OWL['priorVersion'])       : 'schema',
        str(OWL['versionInfo'])        : 'schema',
        
        str(WOT['assurance'])          : 'trust',
        str(WOT['src_assurance'])      : 'trust',
    })
        
class ClassType(ImportType):
    rules = ImportType.rules.copy()
    rules.update({
        str(RDFS['subClassOf'])            : 'schema-hierarchy',
        
        str(DAMLOIL['sameClassAs'])        : 'schema-sets',           
    })
    
class PropertyType(ImportType):
    rules = ImportType.rules.copy()
    rules.update({
        str(RDFS['subPropertyOf'])            : 'schema-hierarchy',
        
        str(DAMLOIL['samePropertyAs'])       : 'schema-sets',
        
        str(RDFS['domain'])                  : 'schema-relations',
        str(RDFS['range'])                   : 'schema-relations',
    })
    

class OntologyImporter(object):
    
    defaultTag = "index"
    defaultRule = 'misc'
        
    importTypes = {
        str(OWL['Ontology']) : OntologyType,
        str(RDFS['Class'])   : ClassType,
        str(OWL['Class'])    : ClassType,
        str(DAMLOIL['Class']): ClassType,
        str(RDF['Property']) : PropertyType,
        str(OWL['DatatypeProperty']) : PropertyType,
        str(OWL['InverseFunctionalProperty']) : PropertyType,
        str(OWL['ObjectProperty']): PropertyType,
        str(OWL['FunctionalProperty']): PropertyType,
    }
    
    defaultRDFDoc = "<RDF xmlns='%s'/>" % str(RDF)
    
    def __init__(self, defaultTag=None):
        if defaultTag:
            self.defaultTag = defaultTag
            
    def applyRelatedRule(self, model, uri, scope):
        for s in model.complete(uri,None,None):
            s.scope = scope
            if s.objectType == OBJECT_TYPE_RESOURCE:                
                if model.isBnodeLabel(s.object):
                    for t in self.applyRelatedRule(
                        model,
                        s.object,
                        s.scope
                    ):
                        yield t
                yield s
                
            else:
                s.object = ' '.join(
                    NORMALIZE_PATSUB.sub(' ',s.object).split()
                )                
                yield s
                
    def applyBnodeRule(self, model, uri, scope):
        for s in model.complete(uri,None,None):
            s.scope = scope
            if s.objectType == OBJECT_TYPE_RESOURCE:                
                if model.isBnodeLabel(s.object):
                    for t in self.applyBnodeRule(
                        model,
                        s.object,
                        s.scope
                    ):
                        yield t
                yield s
                
                
            else:
                s.object = ' '.join(
                    NORMALIZE_PATSUB.sub(' ',s.object).split()
                )                
                yield s
    
    def applyRules(self, rules, model, uri, baseScope):        
        results = {}        
        for s in model.complete(uri,None,None):
            frag = rules.get(s.predicate,self.defaultRule)
            if frag:
                s.scope = '%s#%s' % (baseScope,frag)
            else:
                s.scope = baseScope
            if s.objectType == OBJECT_TYPE_RESOURCE:
                if model.isBnodeLabel(s.object):
                    results.setdefault(s.scope,[]).extend(
                        self.applyBnodeRule(
                            model,
                            s.object,
                            s.scope
                        )
                    )
                    results[s.scope].append(s)
                else:
                    results.setdefault(s.scope,[]).append(s)               
                    
            else:
                s.object = ' '.join(
                    NORMALIZE_PATSUB.sub(' ',s.object).split()
                )                
                results.setdefault(s.scope,[]).append(s)
        return results
    
    def deserializeAndFrag(self, baseUri, srcUri, baseScope):
        from Ft.Rdf.Util import DeserializeFromUri
        model, driver = DeserializeFromUri(srcUri)
        
        processed = set()
        results = {}
        if baseUri[-1] == '/':
            baseUriLen = len(baseUri)
        else:
            # need to account for '#' at end of namespace
            baseUriLen = len(baseUri) + 1
        driver = model.driver
        for uri in driver.subjectsFromPredAndObj(None,None,None): 
            if uri.startswith(baseUri):
                if uri in processed:
                    continue
                rules = {}
                for t in driver.objectsFromSubAndPred(uri,[unicode(RDF['type'])],None):
                    t = self.importTypes.get(t[0])
                    if t:
                        rules.update(t.rules)
                if not rules:
                    rules = ImportType.rules
                
                scope = "%s%s" % (baseScope, uri[baseUriLen:] or self.defaultTag)                                 
                results[(uri,scope)] = self.applyRules(
                    rules,
                    model,
                    uri,
                    scope
                )
                processed.add(uri)
                
            elif not model.isBnodeLabel(uri) and uri not in processed:
                scope = "%s%s" % (baseScope, self.defaultTag)
                results.setdefault((baseUri,scope),{}).setdefault(scope+'#related',[]).extend(
                    self.applyRelatedRule(
                        model,
                        uri,
                        scope + '#related'
                    )
                )
                processed.add(uri)
            
        
        if baseUri not in processed:                       
            rtype = self.importTypes.get(str(OWL['Ontology']))
            if rtype:
                from Ft.Rdf.Statement import Statement 
                model.add(Statement(
                    baseUri,
                    str(RDF['type']),
                    str(OWL['Ontology']),
                    objectType = OBJECT_TYPE_RESOURCE
                ))
                scope = "%s%s" % (baseScope, self.defaultTag)
                results.setdefault((baseUri,scope),{}).update(
                    self.applyRules(
                        rtype.rules,
                        model,
                        baseUri,
                        scope
                    )
                )
                # processed.add(baseUri)
            
                    
        return results
    
    def doImport(self, container, baseUri, srcUri, modelPath=None):
        for (uri,path), stmtMap in self.deserializeAndFrag(
            baseUri,
            srcUri,
            container.getAbsolutePathAsUri() + '/'
        ).items():
            if container.hasResource(path):
                print "EXISTS: %s" % path
                print container.fetchResource(path).getContent()
                print "NEW:"
                print str(stmtMap)
            res = container.createDocument(
                path,
                self.defaultRDFDoc,
                uri =  uri,
                model = modelPath
            )
            model = res.getModel()
            for stmts in stmtMap.values():
                model.add(stmts)